package waggle.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.maps.client.InfoWindow;
import com.google.gwt.maps.client.InfoWindowContent;
import com.google.gwt.maps.client.MapType;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.ControlAnchor;
import com.google.gwt.maps.client.control.ControlPosition;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.control.MenuMapTypeControl;
import com.google.gwt.maps.client.control.ScaleControl;
import com.google.gwt.maps.client.event.MapClickHandler;
import com.google.gwt.maps.client.event.MapDragEndHandler;
import com.google.gwt.maps.client.event.MapTypeChangedHandler;
import com.google.gwt.maps.client.event.MapZoomEndHandler;
import com.google.gwt.maps.client.event.MarkerClickHandler;
import com.google.gwt.maps.client.event.MarkerRemoveHandler;
import com.google.gwt.maps.client.event.MapDragEndHandler.MapDragEndEvent;
import com.google.gwt.maps.client.geocode.LatLngCallback;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.Overlay;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.HorizontalSplitPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import waggle.client.bean.Occurrence;
import waggle.client.bean.OccurrenceSummary;
import waggle.client.maps.OccurrenceMarker;

import waggle.client.DataPager.PageListener;
import waggle.client.PagerWidget.PageClickListener;
import waggle.client.OccurrenceQuery.DataRequestListener;
import waggle.client.DetailView.FieldConstants;
import waggle.client.MarkerList.ItemSelectionListener;
import waggle.client.MarkerList.CheckedSelectionListener;
import waggle.client.MarkerList.OccurrenceItem;

/**
 * A type of view that shows a Google map displaying pageable occurrence data
 * represented as markers.
 */
public class MapView extends ComponentView implements CheckedSelectionListener,
		// kayjean TileLayerCallback,
		DataRequestListener, PageClickListener, PageListener<Occurrence>,
		ItemSelectionListener, SelectionHandler<Integer> {

	// 先完成基本部分
	/**
	 * Initializes the map view and returns it's view info so that it can be
	 * lazy loaded.
	 * 
	 * @param composite
	 *            the parent view
	 * @param query
	 *            {@link OccurrenceQuery} using for this view
	 * @param pageListener
	 *            {@link PageListener} to listener for page changing
	 * @param isDefaultView
	 *            true if this constructed view is default view.
	 * @param occurrenceListener
	 *            {@link OccurrenceListener} to notify {@link Occurrence}
	 *            selection.
	 */
	public static ViewInfo init(final View composite,
			final OccurrenceQuery query,
			final PageListener<Occurrence> pageListener,
			final boolean isDefaultView,
			final OccurrenceListener occurrenceListener) {
		return new ViewInfo() {

			protected View constructView() {
				return new MapView(composite, query, pageListener,
						isDefaultView, occurrenceListener);
			}

			protected String getHisTokenName() {
				return MAP;
			}

			protected String getName() {
				return MAP;
			}
		};
	}

	HorizontalPanel toolHp = new HorizontalPanel();
	private final TabPanel leftTab;
	private final HorizontalSplitPanel hsp;

	/**
	 * The widget that supports paging through occurrence search results.
	 */
	private final PagerWidget<Occurrence> pager;
	private final ActionTool actionTool;
	private static final String DEFAULT_STYLE = "OccurrenceMapView";
	private final VerticalPanel mainVp = new VerticalPanel();
	private int currentSelectedTab = 0;
	private boolean isInitializing = true;

	/**
	 * Creates a new map view. The map view is intended to be part of a
	 * composite view which displays a page of occurrences on a map or in a
	 * list.
	 * 
	 * @param parent
	 *            the parent composite view
	 * @param pageListener
	 *            {@link PageListener} to listener for page changing
	 * @param isDefaultView
	 *            true if this view is default view.
	 * @param occurrenceListener
	 *            {@link OccurrenceListener} to notify {@link Occurrence}
	 *            selection.
	 */
	private MapView(View parent, OccurrenceQuery query,
			PageListener<Occurrence> pageListener, boolean isDefaultView,
			OccurrenceListener oListener) {
		super(parent, isDefaultView);
		setOccurrenceListener(oListener);
		pager = new OccurrencePagerWidget(10, query, true);
		markerList.addCheckedSelectionLsitener(this);
		query.addDataRequestListener(this);
		if (pageListener != null) {
			pager.addPageListener(pageListener);
		}
		pager.addPageClickListener(this);

		pager.addPageListener(this);

		actionTool = new ActionTool(false) {
			protected void setCheckedAll(boolean checked) {
				markerList.setCheckedAll(checked);
			}
		};

		// kayjean envLayerSelector = new EnvLayerSelector(this);
		initMap();
		initMapTypesMap();
		// kayjean modelSearch = new ModelSearch();
		leftTab = new TabPanel();
		leftTab.add(markerList, constants.MarkerResult());
		// kayjean leftTab.add(modelSearch, constants.ModelSearch());
		leftTab.addSelectionHandler(this);
		hsp = new HorizontalSplitPanel();
		hsp.setLeftWidget(leftTab);
		hsp.setRightWidget(map);

		HorizontalPanel toolHp = new HorizontalPanel();
		toolHp.add(actionTool);
		toolHp.add(pager);
		toolHp.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		toolHp.setCellHorizontalAlignment(actionTool,
				HasHorizontalAlignment.ALIGN_LEFT);
		toolHp.setCellHorizontalAlignment(pager,
				HasHorizontalAlignment.ALIGN_RIGHT);
		toolHp.setWidth("100%");
		toolHp.setStyleName(DEFAULT_STYLE + "-ToolBar");
		mainVp.add(toolHp);
		// vp.setCellHorizontalAlignment(pager,
		// HasHorizontalAlignment.ALIGN_RIGHT);
		mainVp.setCellHeight(toolHp, "20px");

		// idPanel.setHeight("20px");
		// vp.add(idPanel);
		// vp.setCellHeight(idPanel, "20px");

		mainVp.add(hsp);
		mainVp.setCellVerticalAlignment(hsp, HasVerticalAlignment.ALIGN_TOP);
		mainVp.setStyleName(DEFAULT_STYLE);
		initWidget(mainVp);
		// mainVp.setPixelSize(Window.getClientWidth(),
		// Window.getClientHeight());
		// mainVp.setSize("100%", "100%");
		hsp.setSplitPosition("50%");

		markerList.addItemSelectionListener(this);
		markerList.setCheckedAll(true);

		String historyToken = History.getToken();
		leftTab.selectTab(currentSelectedTab);
		if (!historyToken.equals("")) {
			handleOnValueChange(historyToken);
		}
		// History.fireCurrentHistoryState();
		DeferredCommand.addCommand(new Command() {
			public void execute() {
				resize(Window.getClientWidth(), Window.getClientHeight());
				isInitializing = false;
				
				
			}

		});
		
	}

	// 要加入這行,才能完整顯示地圖,原因是地圖的顯示視窗大小並沒有被指定
	protected void resize(int width, int height) {
		height = height - mainVp.getAbsoluteTop();
		if (height <= 0) {
			height = 1;
		}
		hsp.setSplitPosition("50%");
		mainVp.setPixelSize(width, height);
		map.checkResizeAndCenter();

		String SOUTHWEST = map.getBounds().getSouthWest().toUrlValue(6);
		String NORTHEAST = map.getBounds().getNorthEast().toUrlValue(6);
		parent.resetpagenum( SOUTHWEST , NORTHEAST );
		handleHistoryEvent( true );
	}

	// 資料進來時候,究竟怎麼顯示呢?
	/**
	 * Get called when data from pager is come backed from {@link DataSwitch}.
	 */
	public void onPageLoaded(List<Occurrence> data, int pageNumber) {
		if (isMyView(History.getToken())) {
			clearOccurrenceMarkers();
			// 主要是這邊
			mapOccurrenceMarkers(data);
			//配合讓地圖移動影響結果 handleHistoryEvent();
			handleHistoryEvent( false );
			markerList.setCheckedFromMap(historyState.getChecksMap());
		}

	}

	// 也會順便加入地圖功能
	private final List<OccurrenceMarker> occurrenceMarkers = new ArrayList<OccurrenceMarker>();
	/**
	 * The Google map widget that initially displays the center of Madagascar.
	 */
	private MapWidget map;
	private final MarkerList markerList = new MarkerList();

	private void clearOccurrenceMarkers() {
		for (Overlay marker : occurrenceMarkers) {
			map.removeOverlay(marker);
		}
		occurrenceMarkers.clear();
		markerList.clear();
	}

	private void mapOccurrenceMarkers(List<Occurrence> occurrences) {
		OccurrenceMarker.resetIcons();
		OccurrenceMarker marker;
		List<Occurrence> unmappableOccs = new ArrayList<Occurrence>();
		for (Occurrence occurrence : occurrences) {
			if (!OccurrenceMarker.isMappable(occurrence)) {
				unmappableOccs.add(occurrence);
				continue;
			}
			occurrenceMarkers.add(marker = new OccurrenceMarker(occurrence));
			markerList.addItem(marker);
			map.addOverlay(marker);
			marker.addMarkerClickHandler(new MarkerClickHandler() {
				public void onClick(MarkerClickEvent event) {
					OccurrenceMarker marker = (OccurrenceMarker) event
							.getSender();
					// 點下去才會啟動
					showWindowInfo(marker);
				}
			});
		}
		markerList.addUnmappableItems(unmappableOccs);
	}

	private void showWindowInfo(OccurrenceMarker occurrenceMarker) {
		if (summaryContent == null) {
			summaryContent = new OccurrenceSummaryContent();
		}
		summaryContent.loadOccurrenceInfo(occurrenceMarker.getOccurrence());
		map.getInfoWindow().open(occurrenceMarker,
				new InfoWindowContent(summaryContent));

	}

	private OccurrenceSummaryContent summaryContent = null;

	/**
	 * Display field for map info window.
	 */
	public static final String USER_MAP_FIELDS[] = { " ", "ReBioMa Id",
			constants.Taxonomy(), constants.Public(), constants.Validated(),
			constants.Certified(), constants.Owner(),
			FieldConstants.DECIMAL_LATITUDE, FieldConstants.DECIMAL_LONGITUDE,
			FieldConstants.LOCALITY, FieldConstants.COUNTRY,
			FieldConstants.STATE_PROVINCE, FieldConstants.COUNTY,
			constants.ValidationError() };

	/**
	 * Display field for map info window.
	 */
	public static final String GUEST_MAP_FIELDS[] = { " ", "ReBioMa Id",
			constants.Taxonomy(), constants.Validated(), constants.Certified(),
			constants.Owner(), FieldConstants.DECIMAL_LATITUDE,
			FieldConstants.DECIMAL_LONGITUDE, FieldConstants.LOCALITY,
			FieldConstants.COUNTRY, FieldConstants.STATE_PROVINCE,
			FieldConstants.COUNTY, constants.ValidationError() };

	/**
	 * The widget displayed in a marker info window.
	 */
	private class OccurrenceSummaryContent extends Composite implements
			ClickHandler {
		private final HTML occurrenceInfo = new HTML();
		private Occurrence occurrence = null;
		private final HTML detailLink = new HTML(constants.Detail()
				+ " &raquo;");
		private final HTML showLayersLink = new HTML(constants.ShowLayers());

		public OccurrenceSummaryContent() {
			detailLink.setStyleName("link");
			showLayersLink.setStyleName("link");
			VerticalPanel vp = new VerticalPanel();
			vp.add(occurrenceInfo);
			vp.add(showLayersLink);
			vp.add(detailLink);
			initWidget(vp);

			detailLink.addClickHandler(this);
			showLayersLink.addClickHandler(this);
		}

		/**
		 * Builds the info window display for an occurrence.
		 * 
		 * @param occurrence
		 *            the occurrence to display in the info window
		 */
		public void loadOccurrenceInfo(Occurrence occurrence) {
			boolean isSignedIn = ApplicationView.getAuthenticatedUser() != null;
			this.occurrence = occurrence;
			OccurrenceSummary occSumm = new OccurrenceSummary(occurrence);
			String infoContent[] = isSignedIn ? occSumm.getMapUserSummary()
					: occSumm.getMapUnauthenticatedSummary();
			StringBuilder sb = new StringBuilder();
			String headers[] = isSignedIn ? USER_MAP_FIELDS : GUEST_MAP_FIELDS;
			for (int i = 1; i < headers.length; i++) {
				String header = headers[i];
				String fieldData;
				if (header.equals(constants.Owner())
						&& !OccurrenceSummary.isEmailVisisble(occurrence)) {
					fieldData = constants.EmailNotShow();
				} else {
					fieldData = infoContent[i - 1];
				}
				if (fieldData == null) {
					fieldData = "";
				}
				if (fieldData.contains(",")) {
					fieldData = "<br>" + fieldData.replace(",", "<br>");
				}
				sb.append("<b>" + header + "</b>: " + fieldData + "<br>");
			}
			sb.delete(sb.length() - 4, sb.length());
			occurrenceInfo.setHTML(sb.toString());
		}

		public void onClick(ClickEvent event) {
			Object source = event.getSource();
			if (source == detailLink) {
				if (occurrence != null) {
					parent.switchView(DETAIL, false);
					map.getInfoWindow().close();
					occurrenceListener.onOccurrenceSelected(occurrence);
				}
			} else if (source == showLayersLink) {
				if (occurrence != null) {
					/*
					 * kayjean LayerInfoPopup layerInfoPopup = LayerInfoPopup
					 * .getInstance();
					 * layerInfoPopup.loadLayersInfo(occurrence);
					 */
					Window.alert("not support");
				}
			}

		}
	}

	private OccurrenceListener occurrenceListener;

	private void setOccurrenceListener(OccurrenceListener occurrenceListener) {
		this.occurrenceListener = occurrenceListener;
		markerList.setOccurrenceListener(occurrenceListener);
	}

	// 地圖
	/**
	 * Far enough out to show the entire country of Madagascar on a 800x600
	 * Google map.
	 */
	private static final int DEFAULT_ZOOM = 7;
	/**
	 * For some reason when make this static it cause a weird infinite
	 * initializing, so make it non-static to solve this problem.
	 */
	// kayjean private final MapType DEFAULT_MAP_TYPE =
	// MapType.getPhysicalMap();
	private final MapType DEFAULT_MAP_TYPE = MapType.getNormalMap();

	private void initMap() {

		map = new MapWidget(HistoryState.DEFAULT_CENTER, DEFAULT_ZOOM,
				"crosshair", "move");
		// 這是原本版本,不能顯示
		map.setWidth("100%");
		map.setHeight("100%");

		map.setCurrentMapType(DEFAULT_MAP_TYPE);
		// map.addControl(getModelControl());
		DeferredCommand.addCommand(new Command() {
			public void execute() {
				// kayjean map.addControl(geocoder);
				// kayjean map.addControl(envLayerSelector);
				ScaleControl scaleControl = new ScaleControl();
				LargeMapControl largeMapControl = new LargeMapControl();
				MenuMapTypeControl mapTypeControl = new MenuMapTypeControl();

				map.addControl(scaleControl);
				map.addControl(largeMapControl);
				map.addControl(mapTypeControl);

				ControlPosition hideControlPosition = new ControlPosition(
						ControlAnchor.TOP_RIGHT, 100, 10);
				// kayjean HideControl hideControl = new
				// HideControl(hideControlPosition);
				// kayjean map.addControl(hideControl);
				// kayjean
				// hideControl.addControlWidgetToHide(geocoder.getControlWidget
				// ());
				// kayjean hideControl.addControlWidgetToHide(envLayerSelector.
				// getControlWidget());
			}
		});
		map.addMapType(DEFAULT_MAP_TYPE);
		// kayjean map.addMapType(MapType.getEarthMap());
		// kayjean map.addMapClickHandler(mapClickHandler);
		map.addMapZoomEndHandler(mapZoomHandler);
		//配合地圖移動新加入部分
		map.addMapDragEndHandler(mapDragHandler);
		
//		map.addMapTypeChangedHandler(mapTypeHandler);
		map.checkResizeAndCenter();
		// kayjean MapType.getEarthMap();
		MapType.getDefaultMapTypes();
	}

	//配合地圖移動新加入部分,map.add也相對調整
	private final MapDragEndHandler mapDragHandler = new MapDragEndHandler() {
		public void onDragEnd(MapDragEndEvent event) {
			//配合讓地圖移動影響結果 handleHistoryEvent();
			//地圖移動或是放大縮小,就要重第一頁開始計算
			String SOUTHWEST = map.getBounds().getSouthWest().toUrlValue(6);
			String NORTHEAST = map.getBounds().getNorthEast().toUrlValue(6);
			parent.resetpagenum( SOUTHWEST , NORTHEAST );
			handleHistoryEvent( true );
		}
	};
	
	
	/**
	 * This is the handler for map zoom events. After a zoom, it issues a
	 * history event.
	 */
	private final MapZoomEndHandler mapZoomHandler = new MapZoomEndHandler() {
		public void onZoomEnd(MapZoomEndEvent event) {
			//配合讓地圖移動影響結果 handleHistoryEvent();
			//地圖移動或是放大縮小,就要重第一頁開始計算
			String SOUTHWEST = map.getBounds().getSouthWest().toUrlValue(6);
			String NORTHEAST = map.getBounds().getNorthEast().toUrlValue(6);
			parent.resetpagenum( SOUTHWEST , NORTHEAST );
			handleHistoryEvent( true );
		}
	};

	/**
	 * This is the handler for map type changes. After a change, it issues a
	 * history event.
	 */
	private final MapTypeChangedHandler mapTypeHandler = new MapTypeChangedHandler() {
		public void onTypeChanged(MapTypeChangedEvent event) {
			//配合讓地圖移動影響結果 handleHistoryEvent();
			handleHistoryEvent( false );
		}
	};

	private void initMapTypesMap() {
		for (MapType mapType : map.getMapTypes()) {
			mapTypesMap.put(mapType.getName(true), mapType);
		}
		// mapTypesMap.put(key, value)
	}

	private final Map<String, MapType> mapTypesMap = new HashMap<String, MapType>();

	private MapType getMapType(String type) {
		MapType mapType = mapTypesMap.get(type);
		if (mapType == null) {
			mapType = DEFAULT_MAP_TYPE;
		}
		return mapType;
	}

	public void setVisible(boolean visible) {
		if (map.getCurrentMapType() == MapType.getEarthMap()) {
			map.setVisible(visible);
		} else {
			map.setVisible(true);
		}
	}

	// history相關
	/**
	 * Get called when a {@link ViewState}, which control by root parent is
	 * changed.
	 * 
	 * @see org.rebioma.client.ComponentView#onStateChanged(org.rebioma.client.View.ViewState)
	 */

	/**
	 * This method is called when history changes and it's responsible for
	 * reconstructing the map view from parameters encoded in the history token
	 * via <code>historyToken()</code>.
	 * 
	 * To avoid new history events during the reconstruction process, it sets
	 * the <code>isHistoryChanging</code> property to true and restores it to
	 * false.
	 */
	protected boolean isMyView(String value) {
		historyState.setHistoryToken(value);
		return historyState.getHistoryParameters(UrlParam.VIEW).toString()
				.equalsIgnoreCase(MAP);
	}

	private boolean switched = false;

	/**
	 * A simple helper method that only adds history events to the composite
	 * view parent if the map view isn't currently restoring the map view from a
	 * history token.
	 */
//kayjean 為了配合讓地圖動作影響搜尋結果,更動
//	private void handleHistoryEvent() {
//		if (!isHistoryChanging && !isInitializing && !switched) {
//			addHistoryItem(false);
//		}
//	}
	private void handleHistoryEvent( boolean issueEvent ) {
		if (!isHistoryChanging && !isInitializing && !switched) {
			addHistoryItem(issueEvent);
		}
	}

	  /**
	   * The composite parent view calls this method when a new history item is
	   * added to the history stack. Basically it encodes the map view state into a
	   * history token.
	   */

	  public String historyToken() {
	    StringBuilder tokens = new StringBuilder();
	    // tokens.append(getUrlToken(UrlParam.ZOOM));
	    // tokens.append(getUrlToken(UrlParam.CENTER));
	    // tokens.append(getUrlToken(UrlParam.ADDRESS));
	    // tokens.append(getUrlToken(UrlParam.LOOKUP_POINT));
	    // tokens.append(getUrlToken(UrlParam.LAYER));
	    for (UrlParam param : UrlParam.values()) {
	      tokens.append(getUrlToken(param));
	    }
	    tokens.deleteCharAt(tokens.length() - 1);
	    return tokens.toString();
	  }
	
	  
	  /**
	   * A helper method used for encoding map view parameters into a history token.
	   * 
	   * @param urlParam the URL parameter to encode
	   */
	  private String getUrlToken(UrlParam urlParam) {
	    String query = urlParam.lower() + "=";
	    switch (urlParam) {
	    case ZOOM:
	      query += map.getZoomLevel();
	      break;
	    case CENTER:
	      query += map.getCenter().toUrlValue(7);
	      break;

//	      //配合地圖移動新加入
//		case SOUTHWEST:
//			query += map.getBounds().getSouthWest().toUrlValue(6);
//			break;
//		case NORTHEAST:
//			query += map.getBounds().getNorthEast().toUrlValue(6);
//			break;
	      
//kayjean	      
//	    case ADDRESS:
//	      String address = geocoder.getAddress();
//	      if ((address != null) && (address.length() > 0)) {
//	        query += geocoder.getAddress();
//	      } else {
//	        query = "";
//	      }
//	      break;
	    case LEFT_TAB:
	      int index = leftTab.getTabBar().getSelectedTab();
	      if (index < 0) {
	        index = 0;
	      }
	      query += index;
	      break;
//kayjean	      
//	    case M_SEARCH:
//	      query += modelSearch.searchBox.getText().trim();
//	      break;
//	    case M_PAGE:
//	      query += modelSearch.getPage();
//	      break;
	    case MAP_TYPE:
	      query += map.getCurrentMapType().getName(true);
	      break;
//kayjean	      
//	    case LOOKUP_POINT:
//	      if (lookupPoint != null) {
//	        query += lookupPoint.toUrlValue(7) + "&"
//	            + UrlParam.LOOKUP_VALUE.lower() + "=" + lookupValue;
//	      } else {
//	        query = "";
//	      }
//	      break;
//	    case LAYER:
//	      if (envLayerInfo != null) {
//	        query += envLayerSelector.getSelectionIndex(envLayerInfo.getName());
//	      } else {
//	        query = "";
//	      }
//	      break;
	    case CHECKED:
	      String checkedValues = historyState.getCheckedValues(true);
	      if (checkedValues.equals("")) {
	        query = "";
	      } else {
	        query += checkedValues;
	      }
	      break;
	    case UNCHECKED:
	      String uncheckedValues = historyState.getCheckedValues(false);
	      if (uncheckedValues.equals("")) {
	        query = "";
	      } else {
	        query += uncheckedValues;
	      }
	      break;
	    default:
	      query = "";
	    }
	    return query.length() == 0 ? query : query + "&";
	  }
	  
	/**
	 * True if the map view is reconstructing it's view from parameters in a
	 * history token. This is needed so that we can disable new history events
	 * while reconstructing the view when a history event is fired.
	 */
	private boolean isHistoryChanging = false;
	
	/**
	 * Manage history states of map View.
	 */
	public class MapState extends HistoryState {

		public Object getHistoryParameters(UrlParam param) {
			switch (param) {
			case LAYER:
			case LEFT_TAB:
			case ZOOM:
			case M_PAGE:
				return integerValue(param);
			case LOOKUP_POINT:
			case CENTER:
				//這裡有可能產生需要內容
				return latLngValue(param);

			//為了配合地圖移動增加
//			case SOUTHWEST:
//				return latLngBoundsValue(param);
//			case NORTHEAST:
//				return latLngBoundsValue(param);
				
			case LOOKUP_VALUE:
			case ADDRESS:
			case VIEW:
				if (isDefaultView && historyToken.trim().equals("")) {
					return MAP;
				}
			case M_SEARCH:
			case MAP_TYPE:
				return stringValue(param);
			}
			return "";
		}

	}

	private final HistoryState historyState = new MapState();

	protected void handleOnValueChange(String historyToken) {
		GWT.log(this.getClass() + " -- onHistoryChanged(): " + historyToken,
				null);
		if (historyToken.length() > 0) {
			isHistoryChanging = true;
			historyState.setHistoryToken(historyToken);
			int zoomLv = (Integer) historyState
					.getHistoryParameters(UrlParam.ZOOM);
			map.setZoomLevel(zoomLv == HistoryState.UNDEFINED ? DEFAULT_ZOOM
					: zoomLv);
			map.setCenter((LatLng) historyState
					.getHistoryParameters(UrlParam.CENTER));
			// geocoder.setAddress();

			// kayjean
			// geocoder.lookupAddress(historyState.getHistoryParameters(UrlParam
			// .ADDRESS)+ "");

			int layerIndex = (Integer) historyState
					.getHistoryParameters(UrlParam.LAYER);
			/*
			 * kayjean if (layerIndex == HistoryState.UNDEFINED) {
			 * envLayerSelector.clearSelection(); envLayerInfo = null; } else {
			 * envLayerInfo = envLayerSelector.selectLayer(layerIndex); if
			 * (envLayerInfo != null) { LatLng point = (LatLng) historyState
			 * .getHistoryParameters(UrlParam.LOOKUP_POINT); String value =
			 * historyState .getHistoryParameters(UrlParam.LOOKUP_VALUE) + "";
			 * TileLayerLegend legend = envLayerInfo.getInstance() .getLegend();
			 * legend.setDisplay(point, value); } }
			 */
			MapType mapType = getMapType(historyState
					.getHistoryParameters(UrlParam.MAP_TYPE)
					+ "");
			map.setCurrentMapType(mapType);
			Integer leftTabIndex = (Integer) historyState
					.getHistoryParameters(UrlParam.LEFT_TAB);
			if (leftTabIndex == null || leftTabIndex < 0) {
				leftTabIndex = 0;
			}
			leftTab.selectTab(leftTabIndex);
			String modelSearchTerm = historyState.getHistoryParameters(
					UrlParam.M_SEARCH).toString();
			Integer modelSearchPage = (Integer) historyState
					.getHistoryParameters(UrlParam.M_PAGE);
			/*
			 * kayjean modelSearch.setPage(modelSearchPage);
			 * modelSearch.search(modelSearchTerm);
			 */
			historyState.parseCheckedUrl();
			isHistoryChanging = false;
		}

		// parent.switchView(MAP, true);
	}

	// ////////////////////state

	public void onStateChanged(ViewState state) {
		if (!isMyView(parent.historyToken())) {
			return;
		}
		// pager.goToPage(pager.getCurrentPageNumber());
	}

	// 隨著MarkerList.ItemSelectionListener延伸而來的
	/**
	 * Gets called when an species id link on a {@link MarkerItem} is clicked.
	 */
	public void onSpeciesSelect(OccurrenceItem item) {
		showWindowInfo(item.getOccurrenceMarker());

	}

	/**
	 * Gets called when an marker image on a {@link MarkerItem} is clicked.
	 */
	public void onMarkerSelect(OccurrenceItem item) {
		showWindowInfo(item.getOccurrenceMarker());

	}

	/**
	 * Gets called when an detail link on a {@link MarkerItem} is clicked.
	 */
	public void onDetailSelect(OccurrenceItem item) {
		parent.switchView(DETAIL, true);
		item.showDetail();

	}

	// 隨著MarkerList.CheckedSelectionListener延伸而來的
	/**
	 * Gets Called when a check box(es) is clicked.
	 */
	public void onCheckedSelect(Map<Integer, Boolean> checksMap) {
		historyState.setChecksMap(checksMap);
		if (!checksMap.isEmpty()) {
			//配合讓地圖移動影響結果 handleHistoryEvent();
			handleHistoryEvent( false );
		}

	}

	// 隨著PagerWidget.PageClickListener延伸而來的
	/**
	 * Gets called when a paging button is clicked.
	 */
	public void onPageClicked() {
		parent.resetToDefaultState();
	}

	// 隨著View延伸而來的
	protected void resetToDefaultState() {
		historyState.clearChecksState();
	}

	// 隨著OccurrenceQuery.DataRequestListener而來的
	/**
	 * Gets called when a query request data from this MapView.
	 */
	public void requestData(int pageNum) {
		if (isMyView(History.getToken())) {
			pager.init(pageNum);
		}

	}

	// 隨著SelectionHandler<Integer>.onSelection(SelectionEvent<Integer>
	public void onSelection(SelectionEvent<Integer> event) {
		Object source = event.getSource();
		if (source == leftTab) {
			if (event.getSelectedItem() != currentSelectedTab) {
				this.currentSelectedTab = event.getSelectedItem();
				addHistoryItem(false);

				/*
				 * kayjean if (currentSelectedTab == 1) {
				 * modelSearch.setPage(1); modelSearch.search(""); } else if
				 * (currentSelectedTab == 0) { modelSearch.clearOverLay(); }
				 */
			}
		}

	}

}
